package org.apache.lucene.index;

import java.io.IOException;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.RAMOutputStream;
import org.apache.lucene.util.UnicodeUtil;

public class TagTermInfosWriter {
  /** The file format version, a negative number. */
  //public static final int FORMAT = 1;

  // Changed strings to true utf8 with length-in-bytes not
  // length-in-chars
  //public static final int FORMAT_VERSION_UTF8_LENGTH_IN_BYTES = -4;

  // NOTE: always change this if you switch to a new format!
  public static final int FORMAT_CURRENT = 1;

  private TagFieldInfos fieldInfos;
  private IndexOutput output;
  //private TagTermInfo lastTi = new TagTermInfo();
  private long size;

  int indexInterval = 128;
  int skipInterval = 16;

  /**
   * Expert: The maximum number of skip levels. Smaller values result in
   * slightly smaller indexes, but slower skipping in big posting lists.
   */
  int maxSkipLevels = 10;

  private long lastIndexPointer;
  private boolean isIndex;
  private byte[] lastTermBytes = new byte[10];
  private int lastTermBytesLength = 0;
  private int lastFieldNumber = -1;
  private int lastTermNum;
  private int lastDocFreq;
  private int numBlocks;
  private int[] starts;
  private int[] docsPerBlock;

  private TagTermInfosWriter other;
  private UnicodeUtil.UTF8Result utf8Result = new UnicodeUtil.UTF8Result();

  TagTermInfosWriter(Directory directory, String segment, TagFieldInfos fis, int interval) throws IOException {
    initialize(directory, segment, fis, interval, false);
    other = new TagTermInfosWriter(directory, segment, fis, interval, true);
    other.other = this;
  }

  private TagTermInfosWriter(Directory directory, String segment, TagFieldInfos fis, int interval, boolean isIndex) throws IOException {
    initialize(directory, segment, fis, interval, isIndex);
  }

  private void initialize(Directory directory, String segment, TagFieldInfos fis, int interval, boolean isi) throws IOException {
    indexInterval = interval;
    fieldInfos = fis;
    isIndex = isi;
    output = directory.createOutput(segment + (isIndex ? ".tii" : ".tis"));
    output.writeInt(FORMAT_CURRENT); // write format
    output.writeLong(0); // leave space for size
    output.writeInt(indexInterval); // write indexInterval
    output.writeInt(skipInterval); // write skipInterval
    output.writeInt(maxSkipLevels); // write maxSkipLevels
    assert initUTF16Results();
  }

  void add(Term term, int termNum, int[] docs) throws IOException {
    UnicodeUtil.UTF16toUTF8(term.text, 0, term.text.length(), utf8Result);
    add(fieldInfos.fieldNumber(term.field), utf8Result.result, utf8Result.length, termNum, docs.length, docs);
  }

  // Currently used only by assert statements
  UnicodeUtil.UTF16Result utf16Result1;
  UnicodeUtil.UTF16Result utf16Result2;

  // Currently used only by assert statements
  private boolean initUTF16Results() {
    utf16Result1 = new UnicodeUtil.UTF16Result();
    utf16Result2 = new UnicodeUtil.UTF16Result();
    return true;
  }

  // Currently used only by assert statement
  private int compareToLastTerm(int fieldNumber, byte[] termBytes, int termBytesLength) {
    if (lastFieldNumber != fieldNumber) {
      final int cmp = fieldInfos.fieldName(lastFieldNumber).compareTo(fieldInfos.fieldName(fieldNumber));
      if (cmp != 0 || lastFieldNumber != -1)
        return cmp;
    }
    UnicodeUtil.UTF8toUTF16(lastTermBytes, 0, lastTermBytesLength, utf16Result1);
    UnicodeUtil.UTF8toUTF16(termBytes, 0, termBytesLength, utf16Result2);
    final int len;
    if (utf16Result1.length < utf16Result2.length)
      len = utf16Result1.length;
    else
      len = utf16Result2.length;

    for (int i = 0; i < len; i++) {
      final char ch1 = utf16Result1.result[i];
      final char ch2 = utf16Result2.result[i];
      if (ch1 != ch2)
        return ch1 - ch2;
    }
    return utf16Result1.length - utf16Result2.length;
  }

  /**
   * Adds a new <<fieldNumber, termBytes>, TermInfo> pair to the set. Term must
   * be lexicographically greater than all previous Terms added. TermInfo
   * pointers must be positive and greater than all previous.
   */
  void add(int fieldNumber, byte[] termBytes, int termBytesLength, int termNum, int docFreq, int[] docs) throws IOException {
    assert compareToLastTerm(fieldNumber, termBytes, termBytesLength) < 0 || (isIndex && termBytesLength == 0 && lastTermBytesLength == 0) : "Terms are out of order: field="
        + fieldInfos.fieldName(fieldNumber)
        + " (number "
        + fieldNumber
        + ")"
        + " lastField="
        + fieldInfos.fieldName(lastFieldNumber)
        + " (number "
        + lastFieldNumber
        + ")"
        + " text="
        + new String(termBytes, 0, termBytesLength, "UTF-8")
        + " lastText="
        + new String(lastTermBytes, 0, lastTermBytesLength, "UTF-8");

    if (!isIndex && size % indexInterval == 0)
      other.add(lastFieldNumber, lastTermBytes, lastTermBytesLength, lastTermNum, lastDocFreq, null); 

    writeTerm(fieldNumber, termBytes, termBytesLength); // write term

    output.writeVInt(docFreq); // write doc freq
    output.writeVInt(termNum);
    if (!isIndex) { 
      TagBlockInfos tagBlockInfos = new TagBlockInfos();
      tagBlockInfos.termNum = termNum;
      
      TagBlocksData tagBlocksData = new TagBlocksData(termNum, docs, skipInterval, maxSkipLevels, starts, docsPerBlock);
      tagBlockInfos.docFreqs = tagBlocksData.getDocFreqs();
      tagBlockInfos.positions = tagBlocksData.getPositions();
      RAMOutputStream blockInfosOutput = new RAMOutputStream();
      tagBlockInfos.write(blockInfosOutput);
      output.writeVInt(tagBlocksData.length());
      output.writeVInt((int)blockInfosOutput.length());
      tagBlocksData.write(output);
      blockInfosOutput.writeTo(output);
    }
    if (isIndex) {
      output.writeVLong(other.output.getFilePointer() - lastIndexPointer);
      lastIndexPointer = other.output.getFilePointer(); // write pointer
    }
    lastFieldNumber = fieldNumber;
    lastTermNum = termNum;
    lastDocFreq = docFreq;
    size++;
  }

  private void writeTerm(int fieldNumber, byte[] termBytes, int termBytesLength) throws IOException {
    // TODO: UTF16toUTF8 could tell us this prefix
    // Compute prefix in common with last term:
    int start = 0;
    final int limit = termBytesLength < lastTermBytesLength ? termBytesLength : lastTermBytesLength;
    while (start < limit) {
      if (termBytes[start] != lastTermBytes[start])
        break;
      start++;
    }
    final int length = termBytesLength - start;
    output.writeVInt(start); // write shared prefix length
    output.writeVInt(length); // write delta length
    output.writeBytes(termBytes, start, length); // write delta bytes
    output.writeVInt(fieldNumber); // write field num
    if (lastTermBytes.length < termBytesLength) {
      byte[] newArray = new byte[(int) (termBytesLength * 1.5)];
      System.arraycopy(lastTermBytes, 0, newArray, 0, start);
      lastTermBytes = newArray;
    }
    System.arraycopy(termBytes, start, lastTermBytes, start, length);
    lastTermBytesLength = termBytesLength;
  }

  /** Called to complete TermInfos creation. */
  void close() throws IOException {
    output.seek(4); // write size after format
    output.writeLong(size);
    output.close();
    if (!isIndex)
      other.close();
  }
}
